<?php

declare(strict_types=1);

namespace Common;

use App\Repository\ProjectConfigRepository;
use Hyperf\AsyncQueue\Driver\DriverFactory;
use Hyperf\AsyncQueue\Job;
use Hyperf\Context\ApplicationContext;
use Hyperf\Coroutine\Coroutine;
use Hyperf\Logger\LoggerFactory;
use Hyperf\Redis\Redis;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

class Common
{
    public static function getConfig(string $key, string $default = ''): string
    {
        return self::container()->get(ProjectConfigRepository::class)
            ->findByKey($key) ?? $default;
    }

    public static function container(): ContainerInterface
    {
        return ApplicationContext::getContainer();
    }

    public static function getConfigByGroup(string $groupCode): array
    {
        $data = self::container()->get(ProjectConfigRepository::class)
            ->getQuery()->where(['group_code' => $groupCode])
            ->get()->keyBy('key');
        if (!$data) {
            return [];
        }
        return $data->toArray();
    }

    public static function setConfig(string $key, string $value): bool
    {
        return self::container()->get(ProjectConfigRepository::class)
            ->updateByKey($key, $value);
    }

    public static function redis(): Redis
    {
        return self::container()->get(Redis::class);
    }

    public static function log(string $name, string $message = '', string $level = LogLevel::INFO)
    {
        $logger = self::container()->get(LoggerFactory::class)->get($name);
        $logger->$level($message);
    }

    public static function logException(string $name, \Throwable $e, string $extra = '')
    {
        $message = $e->getFile() . ':' . $e->getLine() . ' message:' . $e->getMessage() . (empty($extra) ? '' : ' extra:' . $extra);
        self::log($name, $message, LogLevel::ERROR);
    }

    public static function getLogger(string $name): LoggerInterface
    {
        return self::container()->get(LoggerFactory::class)->get($name);
    }

    // JWT生成
    public static function generateJwt(array $payload, int $expire = 0): string
    {
        if ($expire > 0) {
            $payload['exp'] = time() + $expire; // 添加过期时间
        }
        $header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);
        $payload = json_encode($payload);
        $secret = env('JWT_SECRET');

        $base64UrlHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header));
        $base64UrlPayload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($payload));

        $signature = hash_hmac('sha256', "$base64UrlHeader.$base64UrlPayload", $secret, true);
        $base64UrlSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));

        return "Bearer $base64UrlHeader.$base64UrlPayload.$base64UrlSignature";
    }

    // 添加JWT解析方法
    public static function parseJwt(string $jwt): array|false
    {
        $data = explode(' ', $jwt);
        if (count($data) !== 2 || $data[0] !== 'Bearer') {
            return false;
        }
        $jwt = $data[1];
        $parts = explode('.', $jwt);
        if (count($parts) !== 3) {
            return false;
        }

        [$base64UrlHeader, $base64UrlPayload, $base64UrlSignature] = $parts;

        $header = json_decode(base64_decode(str_replace(['-', '_'], ['+', '/'], $base64UrlHeader)), true);
        $payload = json_decode(base64_decode(str_replace(['-', '_'], ['+', '/'], $base64UrlPayload)), true);
        $secret = env('JWT_SECRET');

        $signature = hash_hmac('sha256', "$base64UrlHeader.$base64UrlPayload", $secret, true);
        $base64UrlExpectedSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));
        if ($base64UrlSignature !== $base64UrlExpectedSignature) {
            return false;
        }

        // 验证过期时间
        if (isset($payload['exp']) && $payload['exp'] < time()) {
            return false;
        }

        return $payload;
    }

    public static function lock(string $key, mixed $val = 1, int $expire = 5, int $retry = 3, float $interval = 0.5): bool
    {
        for ($i = 0; $i < $retry; $i++) {
            if (self::redis()->set($key, $val, ['nx', 'ex' => $expire])) {
                return true;
            }
            Coroutine::sleep($interval);
        }
        return false;
    }

    public static function renewLock(string $key, mixed $val = 1, int $expire = 5): bool
    {
        return self::redis()->setex($key, $expire, $val);
    }

    public static function unlock(string $key, mixed $val = 1)
    {
        if (self::redis()->get($key) != $val) {
            return 0;
        }
        return self::redis()->del($key);
    }

    public static function push_redis_job(Job $job, string $queue = 'default', int $delay = 0)
    {
        $driver = self::container()->get(DriverFactory::class)->get($queue);
        return $driver->push($job, $delay);
    }
}
